import pytest
import asyncio
import json
import os
from unittest.mock import Mock, patch, AsyncMock
from pathlib import Path
import tempfile
from dotenv import load_dotenv

from src.validator.Response_validator_withenv import ResponseValidator_withenv


class TestResponseValidatorWithenv:
    """ResponseValidator_withenv 类的单元测试"""

    @pytest.fixture
    def sample_testcase(self):
        """提供示例测试用例数据"""
        return {
            "id": "test_case_1",
            "toolName": "test_tool",
            "input": {"param1": "value1"},
            "description": "Test case description",
            "query": "Test query",
            "expect": {
                "status": "success",
                "validation_rules": [
                    {"type": "contains", "value": "expected_string"}
                ]
            }
        }

    @pytest.fixture
    def mock_config(self):
        """模拟配置文件"""
        return {
            "mcpServers": {
                "test_server": {
                    "command": "python",
                    "args": ["-m", "test_server"],
                    "env": {"TEST_VAR": "test_value"}
                }
            }
        }

    @pytest.fixture
    def temp_testcase_file(self, sample_testcase):
        """创建临时测试用例文件"""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            json.dump([sample_testcase], f)
            temp_path = f.name
        
        yield temp_path
        # 清理
        if os.path.exists(temp_path):
            os.unlink(temp_path)

    @pytest.fixture
    def validator(self, mock_config, temp_testcase_file):
        """创建被测试的验证器实例"""
        load_dotenv()
        with patch('src.client.MakeConfig.Configuration') as mock_config_class:
            mock_config_instance = Mock()
            mock_config_instance.load_config.return_value = mock_config
            mock_config_class.return_value = mock_config_instance
            
            with patch('src.llm.LLM.LLMClient') as mock_llm_class:
                mock_llm_instance = Mock()
                mock_llm_class.return_value = mock_llm_instance
                
                validator = ResponseValidator_withenv(
                    api_key=os.environ.get('LLM_API_KEY'),
                    config_path="test/test_config.json",
                    testcase_path=temp_testcase_file,
                    max_attempts=2
                )
                
                # 模拟 LLM 响应
                mock_llm_instance.get_response.return_value = '{"answer": "yes", "explanation": "Test explanation"}'
                
                yield validator

    @pytest.mark.asyncio
    async def test_initialization(self, validator):
        """测试初始化是否正确"""
        assert validator.max_attempts == 2
        assert validator.testcase_path is not None
        assert len(validator.testcases) == 1
        assert validator.testcases[0]["id"] == "test_case_1"

    @pytest.mark.asyncio
    async def test_load_history_results_existing_file(self, temp_testcase_file, mock_config):
        """测试加载历史结果（文件存在的情况）"""
        # 创建历史结果文件
        history_data = [{"id": "existing_case", "toolName": "test_tool"}]
        history_file = os.path.join(os.path.dirname(temp_testcase_file), "validation_results_eval_env.json")
        
        with open(history_file, 'w', encoding='utf-8') as f:
            json.dump(history_data, f)
        
        try:
            with patch('src.client.MakeConfig.Configuration'):
                with patch('src.llm.LLM.LLMClient'):
                    validator = ResponseValidator_withenv(
                        testcase_path=temp_testcase_file,
                        config_path="test/test_config.json"
                    )
                    
                    assert len(validator.history_results) == 1
                    assert validator.processed_ids == {"existing_case"}
        finally:
            if os.path.exists(history_file):
                os.unlink(history_file)

    @pytest.mark.asyncio
    async def test_load_history_results_missing_file(self, validator):
        """测试加载历史结果（文件不存在的情况）"""
        # 确保历史文件不存在
        history_file = os.path.join(os.path.dirname(validator.testcase_path), "validation_results_eval_env.json")
        if os.path.exists(history_file):
            os.unlink(history_file)
        
        # 重新初始化以触发文件不存在的情况
        with patch('src.client.MakeConfig.Configuration'):
            with patch('src.llm.LLM.LLMClient'):
                validator = ResponseValidator_withenv(
                    testcase_path=validator.testcase_path,
                    config_path="test/test_config.json"
                )
                
                assert validator.history_results == []
                assert validator.processed_ids == set()

    @pytest.mark.asyncio
    async def test_run_method_empty_testcases(self, validator):
        """测试没有测试用例的情况"""
        validator.testcases = []  # 清空测试用例
        
        # 应该不会抛出异常
        await validator.run()

    @pytest.mark.asyncio
    async def test_run_method_with_testcases(self, validator, sample_testcase):
        """测试运行方法（有测试用例的情况）"""
        # 模拟 MCPClient 和工具执行
        with patch('src.client.MCPClient.MCPClient') as mock_mcp_class:
            mock_mcp_instance = AsyncMock()
            mock_mcp_class.return_value = mock_mcp_instance
            mock_mcp_instance.execute_tool.return_value = ["Test output containing expected_string"]
            
            # 模拟 ChatSession
            with patch('src.client.Session.ChatSession') as mock_session_class:
                mock_session_instance = AsyncMock()
                mock_session_class.return_value = mock_session_instance
                mock_session_instance.handle_query.return_value = (True, {"tool_name": "test_tool"}, "Session result")
                
                await validator.run()
                
                # 验证结果文件是否创建
                result_file = os.path.join(os.path.dirname(validator.testcase_path), "validation_results_eval_env.json")
                assert os.path.exists(result_file)
                
                # 验证文件内容
                with open(result_file, 'r', encoding='utf-8') as f:
                    results = json.load(f)
                    assert len(results) == 1
                    assert results[0]["id"] == "test_case_1"

    @pytest.mark.asyncio
    async def test_tool_validation_success(self, validator, sample_testcase):
        """测试工具验证成功的情况"""
        with patch('src.client.MCPClient.MCPClient') as mock_mcp_class:
            mock_mcp_instance = AsyncMock()
            mock_mcp_class.return_value = mock_mcp_instance
            mock_mcp_instance.execute_tool.return_value = ["Test output containing expected_string"]
            
            result = await validator.tool_validation(sample_testcase, "test_server")
            
            assert result["passed"] is True
            assert result["id"] == "test_case_1"

    @pytest.mark.asyncio
    async def test_tool_validation_timeout(self, validator, sample_testcase):
        """测试工具验证超时的情况"""
        with patch('src.client.MCPClient.MCPClient') as mock_mcp_class:
            mock_mcp_instance = AsyncMock()
            mock_mcp_class.return_value = mock_mcp_instance
            
            # 模拟超时
            mock_mcp_instance.execute_tool.side_effect = asyncio.TimeoutError()
            
            # 设置较短的超时时间以便测试
            validator.TOOL_VALIDATION_TIMEOUT = 0.1
            
            result = await validator.tool_validation(sample_testcase, "test_server")
            
            assert result["passed"] is False
            assert "超时" in result["message"]

    @pytest.mark.asyncio
    async def test_validate_toolcase_no_output(self, validator, sample_testcase):
        """测试工具用例验证（无输出情况）"""
        with patch('src.client.MCPClient.MCPClient') as mock_mcp_class:
            mock_mcp_instance = AsyncMock()
            mock_mcp_class.return_value = mock_mcp_instance
            mock_mcp_instance.execute_tool.return_value = None  # 无输出
            
            result = await validator.validate_toolcase(mock_mcp_instance, sample_testcase)
            
            assert result["passed"] is False
            assert "No output received" in result["message"]

    def test_validate_single_rule_contains(self, validator):
        """测试单个规则验证（contains类型）"""
        output = ["This is a test output with expected_string"]
        rule = {"type": "contains", "value": "expected_string"}
        case = {"id": "test_case"}
        
        valid, message = validator.validate_single_rule(output, rule, case)
        
        assert valid is True
        assert message == ""

    def test_validate_single_rule_contains_failure(self, validator):
        """测试单个规则验证（contains类型失败）"""
        output = ["This is a test output without the string"]
        rule = {"type": "contains", "value": "expected_string"}
        case = {"id": "test_case"}
        
        valid, message = validator.validate_single_rule(output, rule, case)
        
        assert valid is False
        assert "expected_string" in message

    def test_validate_single_rule_equals(self, validator):
        """测试单个规则验证（equals类型）"""
        output = ["expected_value"]
        rule = {"type": "equals", "value": ["expected_value"]}
        case = {"id": "test_case"}
        
        valid, message = validator.validate_single_rule(output, rule, case)
        
        assert valid is True
        assert message == ""

    def test_validate_single_rule_schema(self, validator):
        """测试单个规则验证（schema类型）"""
        output = [{"name": "test", "value": 123}]
        rule = {
            "type": "schema", 
            "value": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "value": {"type": "number"}
                }
            }
        }
        case = {"id": "test_case"}
        
        valid, message = validator.validate_single_rule(output, rule, case)
        
        assert valid is True
        assert "passed" in message

    def test_validate_single_rule_llm(self, validator):
        """测试单个规则验证（llm类型）"""
        output = "Test output"
        rule = {"type": "llm", "value": "Check if output is valid"}
        case = {"id": "test_case", "toolName": "test_tool", "input": {"param": "value"}}
        
        # 模拟 LLM 响应
        validator.llm.get_response.return_value = '{"answer": "yes", "explanation": "Valid output"}'
        
        valid, message = validator.validate_single_rule([output], rule, case)
        
        assert valid is True
        assert "Valid output" in message

    def test_clean_value(self, validator):
        """测试清理值函数"""
        test_cases = [
            ('"test"', 'test'),
            (' test ', 'test'),
            ('test!@#$', 'test'),
            ('', ''),
        ]
        
        for input_val, expected in test_cases:
            result = validator.clean_value(input_val)
            assert result == expected

    @pytest.mark.asyncio
    async def test_eval_validation_success(self, validator, sample_testcase):
        """测试端到端验证成功的情况"""
        # 模拟服务器和会话
        mock_server = AsyncMock()
        validator.server = mock_server
        
        with patch('src.client.Session.ChatSession') as mock_session_class:
            mock_session_instance = AsyncMock()
            mock_session_class.return_value = mock_session_instance
            mock_session_instance.handle_query.return_value = (
                True, 
                {"tool_name": "test_tool"}, 
                "Session result containing expected content"
            )
            
            result = await validator.eval_validation(sample_testcase, "test_server")
            
            assert result["passed"] is True
            assert result["toolName"] == "test_tool"

    @pytest.mark.asyncio
    async def test_eval_validation_no_tool_called(self, validator, sample_testcase):
        """测试端到端验证（未调用工具的情况）"""
        # 模拟服务器和会话
        mock_server = AsyncMock()
        validator.server = mock_server
        
        with patch('src.client.Session.ChatSession') as mock_session_class:
            mock_session_instance = AsyncMock()
            mock_session_class.return_value = mock_session_instance
            mock_session_instance.handle_query.return_value = (False, {}, "Session result")
            
            result = await validator.eval_validation(sample_testcase, "test_server")
            
            assert result["passed"] is False
            assert "No tool was called" in result["message"]

    def test_truncate_output_list(self, validator):
        """测试截断输出（列表类型）"""
        long_list = [f"item_{i}" for i in range(20)]  # 20个元素的列表
        result = validator.truncate_output(long_list)
        
        assert len(result) == 10  # 应该被截断到10个元素

    def test_truncate_output_string(self, validator):
        """测试截断输出（字符串类型）"""
        long_string = "a" * 1500  # 1500个字符的字符串
        result = validator.truncate_output(long_string)
        
        assert len(result) < 1100  # 应该被截断
        assert "..." in result  # 应该包含省略号

    def test_save_to_file_success(self, validator):
        """测试保存到文件成功的情况"""
        test_data = [{"id": "test1", "result": "success"}]
        
        result = validator.save_to_file("test_server", test_data)
        
        assert result is True
        
        # 验证文件是否创建
        result_file = os.path.join(os.path.dirname(validator.testcase_path), "validation_results_eval_env.json")
        assert os.path.exists(result_file)

    def test_save_to_file_invalid_data(self, validator):
        """测试保存到文件（无效数据的情况）"""
        # 传递非列表数据应该返回 False
        result = validator.save_to_file("test_server", "invalid_data")
        assert result is False

    def test_get_dependencies_found(self, validator):
        """测试获取依赖项（找到requirements.txt的情况）"""
        # 创建临时目录和requirements.txt
        with tempfile.TemporaryDirectory() as temp_dir:
            # 模拟服务器的 host_mcp_path
            mock_server = Mock()
            mock_server.host_mcp_path = temp_dir
            
            # 创建 requirements.txt
            req_file = Path(temp_dir) / "requirements.txt"
            req_file.write_text("pytest==7.0.0\nrequests==2.25.0")
            
            dependencies = validator.get_dependencies(mock_server)
            
            assert "pytest==7.0.0" in dependencies
            assert "requests==2.25.0" in dependencies

    def test_get_dependencies_not_found(self, validator):
        """测试获取依赖项（未找到requirements.txt的情况）"""
        # 创建临时目录（不包含requirements.txt）
        with tempfile.TemporaryDirectory() as temp_dir:
            mock_server = Mock()
            mock_server.host_mcp_path = temp_dir
            
            dependencies = validator.get_dependencies(mock_server)
            
            assert dependencies == ""


if __name__ == "__main__":
    # 运行测试
    pytest.main([__file__, "-v"])